package cn.lx.sequence.strategy;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import redis.clients.jedis.JedisCluster;
import sun.dc.pr.PRError;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 生成器对象
 * @author lX
 * @version JDK 8
 * @className IDGenerator (此处以class为例)
 * @date 2024/5/27
 * @description TODO
 */
@Component
@Slf4j
public class Generator {


    /**
     * redisTemplate模板类
     */
    private RedisTemplate redisTemplate;

    /**
     * 序列名称
     */
    private String seqName;
    /**
     * 当前数
     */
    private volatile long current;
    /**
     * 当前机器缓存最大数
     */
    private volatile long max;
    /**
     * 步长
     */
    private volatile int step;
    /**
     * 重试次数
     */
    private volatile int retryTime = 3;


    public void init(String seqName, int step,RedisTemplate redisTemplate) {
        if (StringUtils.isEmpty(seqName)) {
            throw new IllegalArgumentException("序列名称为空,请检查参数");
        } else {
            this.seqName = seqName;
            this.step = step;
            this.redisTemplate = redisTemplate;
        }
    }

    /**
     * 本地锁生成单个序列
     * @return
     */
    public synchronized long next() {
        if (this.current >= this.max) {
            this.max = this.getMaxByRedis(seqName,step);
            this.current = this.max - this.step;
        }

        ++this.current;
        return this.current;
    }

    /**
     * 本地锁生成多个序列
     * 一次拿的不能超过步长 要不可能会导致ID重复
     * 比如步长为1000  一次性拿2000
     * 本机判断 生成1000的数 但是本地可以返回0-2000的数
     * 另外一台机器也是1000 此时redis为1000，根据步长  生成2000的数
     * 那么1000-2000的数就是有重复的
     * @param size
     * @return
     */
    public synchronized List<Long> nextList(int size) {
        if(size>step){
            throw new RuntimeException("生成序列失败,批量获取序列数不能超过步长");
        }
        List<Long> currentList = new ArrayList();
        if (this.current + size >= this.max) {
            this.max = this.getMaxByRedis(seqName,step);
            this.current = this.max - this.step;
        }

        for(int i = 0; i < size; ++i) {
            ++this.current;
            currentList.add(this.current);
        }

        return currentList;

    }

    /**
     * 从redis中获取最大序列
     * @param seqName
     * @param step
     * @return
     */
    public long getMaxByRedis(String seqName,int step){

        long increment = 0L;

        for (int i = 0; i < retryTime; ++i) {
            try {
                increment = redisTemplate.opsForValue().increment(seqName, step);
                if (increment > 0L) {
                    break;
                }
            } catch (Exception e) {
                log.error("Redis生成序列失败,重试次数:{},异常信息:{}",i,e.getStackTrace());
            }

        }
        return increment;
    }
}
